package org.jmc.refmanager.util;

import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;

public final class BeanUtil {
	private static Map<CacheKey, Method> propertyMethodCache = new ConcurrentHashMap<CacheKey, Method>();

	
	/**
	 * Sérialisation d'un objet marqé avec les annotations SimpleXML
	 * 
	 * @return
	 */
	public static final <E> String getSerializedBean(E object) {
		Serializer serializer = new Persister();
		StringWriter writer = new StringWriter();
		
		try {
			serializer.write(object, writer);
		} catch (Exception e) {
			throw new IllegalStateException("Impossible de sérialiser l'objet", e);
		}

		StringBuffer xml = writer.getBuffer();
		return xml.toString();
	}

	/**
	 * 
	 * @param <E>
	 * @param xmlObject
	 * @param E
	 * @return
	 */
	public final static <E> E getDeserializedBean(String xmlObject, Class<? extends E> E) {
		Serializer serializer = new Persister();
		E deserializedObject = null;
		try {
			deserializedObject = serializer.read(E, xmlObject);
		} catch (Exception e) {
			throw new IllegalStateException(
					String.format("Impossible de desérialiser l'objet de type %s", E), e);
		}
		
		return deserializedObject;
	}

	/**
	 * 
	 * @param obj
	 * @param name
	 * @param value
	 */
	public final static void setProperty(Object obj, String name, Object value) {
		try {
			CacheKey key = new CacheKey(obj, name, value);
			Method method = propertyMethodCache.get(key);
	
			if (method == null) {
				method = getAttributeAccessor(obj.getClass(), name, value!=null?value.getClass():null, "set");
				propertyMethodCache.put(key, method);
			}
	
			method.invoke(obj, value);
		} catch (Exception e) {
			throw new RuntimeException(
					String.format("Erreur lors de l'invocation dynamique du modificateur de propriété '%s'", name), e);
		}
	}

	/**
	 * 
	 * @param obj
	 * @param name
	 * @return
	 */
	public static Object getProperty(Object obj, String name) {
		Object value = null;

		StringBuffer getterName = new StringBuffer(name.length() + 3);
		getterName.append("get");

		if (Character.isLowerCase(name.charAt(0))) {
			getterName.append(Character.toUpperCase(name.charAt(0))).append(
					name.substring(1));
		} else {
			getterName.append(name);
		}

		try {
			Method method = obj.getClass().getMethod(getterName.toString(), (Class[]) null);
	
			if (method != null) {
				value = method.invoke(obj, (Object[]) null);
			}
			return value;
		} catch (Exception e) {
			throw new RuntimeException(
					String.format("Erreur lors de l'invocation dynamique de l'accesseur de propriété '%s'", name), e);
		}
	}

	/**
	 * Retour le setter de l'attribut. si la methode n'est trouve, l'expection
	 * <code>NoSuchMethodException</code> est soulevee.
	 * 
	 * @param cls
	 *            la classe du bean
	 * @param attr
	 *            le nom de l'attribut
	 * @param type
	 *            le type de l'attribut
	 * @return la methode de set de l'atribut
	 * @throws NoSuchMethodException
	 *             Si le setter n'est trouve
	 */
	private final static Method getAttributeAccessor(Class<?> cls, String attr, Class<?> type, String accessor) throws NoSuchMethodException {
		StringBuffer buf = new StringBuffer(attr.length() + 3);
		buf.append(accessor);

		if (Character.isLowerCase(attr.charAt(0))) {
			buf.append(Character.toUpperCase(attr.charAt(0))).append(
					attr.substring(1));
		} else {
			buf.append(attr);
		}

		if (type != null) {
			return cls.getMethod(buf.toString(), new Class[] { type });
		} else {
			return cls.getMethod(buf.toString(), new Class[] { String.class });
		}
	}


	/**
	 * 
	 * Gestion du cache de methode (l'usage de la classe 
	 * Reflection est couteux).
	 * 
	 */
	private static class CacheKey {
		private Class<? extends Object> _objClass;
		private String _propName;
		private Class<? extends Object> _valueClass;
		private boolean _isBooleanString;
		private int _hashCode;

		public CacheKey(Object obj, String name, Object value) {
			this._objClass = obj.getClass();
			this._propName = name;
			this._valueClass = (value == null ? NULL.class : value.getClass());
			if (this._valueClass == String.class) {
				if (("true".equals(value)) || ("false".equals(value))) {
					this._isBooleanString = true;
				}
			}
			this._hashCode ^= this._objClass.hashCode();
			this._hashCode ^= this._propName.hashCode();
			this._hashCode ^= this._valueClass.hashCode();
		}

		public int hashCode() {
			return this._hashCode;
		}

		public boolean equals(Object o) {
			if ((o instanceof CacheKey)) {
				CacheKey other = (CacheKey) o;
				if (this._objClass != other._objClass) {
					return false;
				}
				if (this._valueClass != other._valueClass) {
					return false;
				}
				if (this._isBooleanString != other._isBooleanString) {
					return false;
				}
				return this._propName.equals(other._propName);
			}
			return false;
		}

		private static class NULL {
		}
	}
}
